package org.dromara.common.mybatis.interceptor;

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.dromara.common.mybatis.handler.PlusDataPermissionHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：数据权限拦截器
 */
/**
 * PlusDataPermissionInterceptor 类继承自 BaseMultiTableInnerInterceptor，并实现了 InnerInterceptor 接口，
 * 用于数据权限的拦截处理。它主要用于处理多表数据权限的校验和过滤，确保只有符合权限要求的数据
 * 能够被访问和操作。
 */
@Slf4j
public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {

    /**
     * 数据权限过滤
     */
    private final PlusDataPermissionHandler dataPermissionHandler;

    /**
     * 构造函数，初始化 PlusDataPermissionHandler 实例
     *
     * @param mapperPackage 扫描的映射器包
     */
    public PlusDataPermissionInterceptor(String mapperPackage) {
        this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
    }

    /**
     * 在执行查询之前，检查并处理数据权限相关逻辑
     *
     * @param executor      MyBatis 执行器对象
     * @param ms            映射语句对象
     * @param parameter     方法参数
     * @param rowBounds     分页对象
     * @param resultHandler 结果处理器
     * @param boundSql      绑定的 SQL 对象
     * @throws SQLException 如果发生 SQL 异常
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        // 检查是否需要忽略数据权限处理
        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
            return;
        }
        // 检查是否缺少有效的数据权限注解
        if (dataPermissionHandler.invalid(ms.getId())) {
            return;
        }
        // 解析 sql 分配对应方法
        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
    }

    /**
     * 在准备 SQL 语句之前，检查并处理更新和删除操作的数据权限相关逻辑
     *
     * @param sh                 MyBatis StatementHandler 对象
     * @param connection         数据库连接对象
     * @param transactionTimeout 事务超时时间
     */
    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        // 获取 MyBatis Plus 的 StatementHandler 实现，用于后续操作
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        // 获取映射的 SQL 语句对象，包含 SQL 语句的相关信息
        MappedStatement ms = mpSh.mappedStatement();
        // 获取 SQL 命令类型（增、删、改、查）
        SqlCommandType sct = ms.getSqlCommandType();

        // 只处理更新和删除操作的 SQL 语句
        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            // 检查当前操作是否被配置为忽略数据权限控制
            if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
                return;
            }
            // 检查是否缺少有效的数据权限注解
            if (dataPermissionHandler.invalid(ms.getId())) {
                return;
            }
            // 对 SQL 语句进行解析和重构，以适应数据权限控制的需要
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
        }
    }

    /**
     * 处理 SELECT 查询语句中的 WHERE 条件
     * 该方法用于根据传入的 SELECT 语句类型设置 WHERE 条件
     * 主要处理两种情况：单个普通的 SELECT 语句和包含多个 SELECT 语句的集合操作
     *
     * @param select SELECT 查询对象，可以是单个 SELECT 语句或包含多个 SELECT 的集合操作
     * @param index  查询语句的索引 语句在集合中的索引位置
     * @param sql    查询语句 执行 SQL 语句的字符串形式
     * @param obj    WHERE 条件参数 传递给 SELECT 语句的参数对象，通常用于设置 WHERE 条件
     */
    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        // 判断是否为普通的 SELECT 语句
        if (select instanceof PlainSelect) {
            // 设置 WHERE 条件
            this.setWhere((PlainSelect) select, (String) obj);
        } else if (select instanceof SetOperationList setOperationList) {
            // 获取包含多个 SELECT 语句的列表
            List<Select> selectBodyList = setOperationList.getSelects();
            // 对每个 SELECT 语句设置 WHERE 条件
            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
        }
    }

    /**
     * 处理 UPDATE 语句中的 WHERE 条件
     * 处理数据权限更新操作
     * 该方法在执行更新操作前，根据数据权限规则动态修改更新语句的条件部分
     *
     * @param update UPDATE 查询对象 待执行的更新操作对象
     * @param index  查询语句的索引 更新操作的索引位置，用于支持批处理中的定位
     * @param sql    查询语句 保留参数，用于兼容性
     * @param obj    WHERE 条件参数 传递给更新操作的附加对象，用于数据权限条件的构建
     */
    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        // 根据数据权限规则和传入的附加对象，构建数据权限的SQL片段
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
        // 如果构建的数据权限SQL片段不为空，则用其替换原有的更新条件
        if (null != sqlSegment) {
            update.setWhere(sqlSegment);
        }
    }

    /**
     * 处理 DELETE 语句中的 WHERE 条件
     * 处理删除操作，在执行删除前根据数据权限动态调整WHERE条件
     * 此方法的目的是确保只有满足特定条件的数据会被删除，从而实现数据权限的控制
     *
     * @param delete 删除操作对象，包含待调整的WHERE条件
     * @param index  SQL语句中的占位符索引
     * @param sql    原始SQL语句
     * @param obj    WHERE 条件参数 用于调整WHERE条件的相关对象，具体用途依赖于实现细节
     */
    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        // 根据提供的对象和当前操作的数据权限规则获取调整后的SQL段
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
        // 如果调整后的SQL段不为空，则用其替换原始的WHERE条件
        if (null != sqlSegment) {
            delete.setWhere(sqlSegment);
        }
    }

    /**
     * 设置 SELECT 语句的 WHERE 条件
     * 设置查询条件
     * 本方法主要用于处理数据权限下的查询条件设置
     * 通过dataPermissionHandler获取与数据权限相关的SQL片段，并将其设置到PlainSelect的查询条件中
     *
     * @param plainSelect       SELECT 查询对象，用于构建SQL查询语句的PlainSelect对象
     * @param mappedStatementId 映射语句的 ID，映射语句的唯一标识，用于数据权限处理
     */
    protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
        // 调用dataPermissionHandler的getSqlSegment方法，根据数据权限规则获取SQL片段
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
        // 如果获取到的SQL片段不为空，则设置到PlainSelect的查询条件中
        if (null != sqlSegment) {
            plainSelect.setWhere(sqlSegment);
        }
    }

    /**
     * 构建表达式，用于处理表的数据权限
     * 该方法仅在使用新版数据权限处理器时被调用
     *
     * @param table        表对象，表信息对象，包含表的详细信息
     * @param where        WHERE 条件表达式，查询条件表达式，用于过滤数据
     * @param whereSegment WHERE 条件片段，SQL语句中的WHERE子句之后的条件部分，可能包含用户自定义的过滤条件
     * @return 构建的表达式，返回根据数据权限规则拼接的SQL片段
     */
    @Override
    public Expression buildTableExpression(Table table, Expression where, String whereSegment) {
        // 只有新版数据权限处理器才会执行到这里
        // 获取新版数据权限处理器实例
        final MultiDataPermissionHandler handler = (MultiDataPermissionHandler) dataPermissionHandler;
        // 调用处理器的方法，根据表信息和条件表达式获取SQL片段
        return handler.getSqlSegment(table, where, whereSegment);
    }
}

